#函数
#1.定义一个函数
# def my_fun(x):
	# print("my first function , param is %s" % x)
# my_fun("123")

# def my_abs(x):
	# if x >= 0:
		# return x
	# else:
		# return -x
# print(my_abs(-15))

# 2.默认参数
# def enroll(name, gender, age=25, city="BeiJing"):
	# print('name:',name)
	# print('gender:',gender)
	# print('age:',age)
	# print('city:',city)
# enroll("Rzh","上学")
# enroll("Rzy","毕业", city="ShanXi")
# 注意默认参数的使用， 使用不当容易掉坑
def add_end(L=[]):
	L.append('aaa')
	return L
print(add_end([1, 2, 3])) #m没有问题
print(add_end(['x', 'y', 'z'])) #依然没有问题
print(add_end()) #第一次用默认值的时候没有问题
print(add_end()) # 第二次使用默认值的时候问题出现了,函数似乎每次都“记住了”上次添加了'aaa'后的list。why? 
# Python函数在定义的时候，默认参数L的值就被计算出来了，即[]，因为默认参数L也是一个变量，它指向对象[]，每次调用该函数，如果改变了L的内容，则下次调用时，默认参数的内容就变了，不再是函数定义时的[]了。
# 所以定义默认参数要牢记一点：默认参数必须指向不变对象！
# 要修改上面的例子，我们可以用None这个不变对象来实现：
def add_end(L=None):
	if L is None:
		L = []
	L.append('aaa')
	return L
print(add_end())
print(add_end()) 
# 为什么要设计str、None这样的不变对象呢？因为不变对象一旦创建，对象内部的数据就不能修改，这样就减少了由于修改数据导致的错误。此外，由于对象不变，多任务环境下同时读取对象不需要加锁，同时读一点问题都没有。我们在编写程序时，如果可以设计一个不变对象，那就尽量设计成不变对象。
print("-------------------------------------------------------------------------------")

# 3.可变参数
# 计算a²+b²+c²
def calc(number):
	sum = 0
	for n in number:
		sum = sum + n*n
	return sum
print(calc([1,2,3]))	# 调用的时候，需要先组装出一个list或tuple
# 如果利用可变参数，调用函数的方式可以简化成这样：>>> calc(1, 2, 3)
# 所以，我们把函数的参数改为可变参数：
def calc(*number):
	sum = 0
	for n in number:
		sum = sum + n*n
	return sum
print(calc(1,2,3,5))
# 定义可变参数和定义一个list或tuple参数相比，仅仅在参数前面加了一个*号。在函数内部，参数numbers接收到的是一个tuple，因此，函数代码完全不变。但是，调用该函数时，可以传入任意个参数，包括0个参数
#如果已经有一个list或者tuple，要调用一个可变参数怎么办？Python允许你在list或tuple前面加一个*号，把list或tuple的元素变成可变参数传进去
nums = [1,2,3,4]
print(calc(*nums))
print("-------------------------------------------------------------------------------")

# 4.关键参数 :可变参数允许你传入0个或任意个参数，这些可变参数在函数调用时自动组装为一个tuple。而关键字参数允许你传入0个或任意个含参数名的参数，这些关键字参数在函数内部自动组装为一个dict。
def person(name, age, **aa):
	print('name:', name, 'age:', age, 'other', aa)
person('rzy', 25, city='bj')
person('rzy', 25, city='bj', job='程序员') #可以传入任意个数的关键字参数
# 上面复杂的调用可以用简化的写法
extra = {'city':'sx', 'job':'程序员'}
person('rzy', 25, **extra)

# 5.命名关键字参数
# 对于关键字参数，函数的调用者可以传入任意不受限制的关键字参数。至于到底传入了哪些，就需要在函数内部通过aa检查。
# 如果要限制关键字参数的名字，就可以用命名关键字参数，例如，只接收city和job作为关键字参数。这种方式定义的函数如下：
def userinfo(name, age, *, city, job):
	print(name, age, city, job)
userinfo('rzy', 25, city='beijing', job='CEO')
# 如果函数定义中已经有了一个可变参数，后面跟着的命名关键字参数就不再需要一个特殊分隔符*了：
def userinfo2(name, age, *param, city, job):
	print(name, age, param, city, job)
userinfo2('rzy', 25, [1,2,3], city='beijing', job='CEO')
# 命名关键字参数必须传入参数名，这和位置参数不同。如果没有传入参数名，调用将报错:
# userinfo('rzy', 25, 'beijing', job='CEO')
# 命名关键字参数可以有缺省值，从而简化调用：
def userinfo3(name, age, *, city="beijing", job):
	print(name, age, city, job)
userinfo3('rzy', 25, job='CEO')
# 使用命名关键字参数时，要特别注意，如果没有可变参数，就必须加一个*作为特殊分隔符。如果缺少*，Python解释器将无法识别位置参数和命名关键字参数
print("-------------------------------------------------------------------------------")

# 5.参数组合
# 在Python中定义函数，可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数，这5种参数都可以组合使用。但是请注意，参数定义的顺序必须是：必选参数、默认参数、可变参数、命名关键字参数和关键字参数。
def f1(a, b, c=20, *args, **kw):
	print("a=", a, "b=", b, "c=", c, "args=", args, "kw=", kw)
args = (1, 2, 3, 4)
kw = {'d': 99, 'x': '#'}
f1(*args, **kw)  # 相当于 f1(args[0], args[1], args[2], args[3], **kw) 
f1(1,*args, **kw)
 

# Python的函数具有非常灵活的参数形态，既可以实现简单的调用，又可以传入非常复杂的参数。

# 默认参数一定要用不可变对象，如果是可变对象，程序运行时会有逻辑错误！

# 要注意定义可变参数和关键字参数的语法：

# *args是可变参数，args接收的是一个tuple；

# **kw是关键字参数，kw接收的是一个dict。

# 以及调用函数时如何传入可变参数和关键字参数的语法：

# 可变参数既可以直接传入：func(1, 2, 3)，又可以先组装list或tuple，再通过*args传入：func(*(1, 2, 3))；

# 关键字参数既可以直接传入：func(a=1, b=2)，又可以先组装dict，再通过**kw传入：func(**{'a': 1, 'b': 2})。

# 使用*args和**kw是Python的习惯写法，当然也可以用其他参数名，但最好使用习惯用法。

# 命名的关键字参数是为了限制调用者可以传入的参数名，同时可以提供默认值。

# 定义命名的关键字参数在没有可变参数的情况下不要忘了写分隔符*，否则定义的将是位置参数。



















